package com.abel.bigwater.tlv.model

import com.alibaba.fastjson.annotation.JSONField
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory
import kotlin.experimental.and

/**
 * 这部分AES加密:
 * 协议版本号	BYTE
指令	BYTE
数据长度	BYTE[2]
TLV数据	BYTE[n]
TLV数据	BYTE[n]
...
数据默认为从高位到低位
 */
class TlvDevBody(
        var version: Short = 1,
        var command: Short = 0,
        var length: Int = 0,
        var dataList: ArrayList<TlvData>) : TlvDataApi {
    @JSONField(serializeUsing = TlvExceptionSerializer::class)
    var parseResult: TlvException? = null

    private val log = LoggerFactory.getLogger(TlvDevBody::class.java)

    companion object {
        val MIN_LEN = 1 + 1 + 2
    }

    override fun finalLen(): Int {
        length = 0
        for (p1 in dataList) {
            length = length + p1.finalLen()
        }

        return MIN_LEN + length
    }

    override fun fill(buf: ByteBuf): ByteBuf {
        finalLen()

        with(buf.alloc().buffer()) {
            writeByte(version.toInt())
            writeByte(command.toInt())
            writeShort(length)

            for (p1 in dataList) {
                p1.fill(this)
            }

            buf.writeBytes(this)
        }

        return buf
    }

    /**
     * buf is consumed dedicately.
     */
    override fun parse(buf: ByteBuf): ByteBuf {
        with(buf.duplicate()) {
            version = readByte().toShort() and 0xFF
            command = readByte().toShort() and 0xFF
            length = readShort().toInt() and 0xFFFF
            if (readableBytes() < length) throw TlvInvalidException("Invalid bytes for tlv-body: ${readableBytes()} < ${length}")

            buf.skipBytes(1 + 1 + 2)
        }

        val pdu = buf.readBytes(length)
        while (pdu.readableBytes() > 0) {
            var dataLen = 2
            var tag: Short = 0

            with(pdu.duplicate()) {
                tag = readByte().toShort() and 0xFF
                if (tag != ResultCodeParam.TAG) {
                    dataLen = readShort().toInt() and 0xFFFF

                    // count tag-length
                    dataLen += 1 + 2
                }
            }
            // the following bytes are consumed to avoid indefinite loop
            if (pdu.readableBytes() < dataLen) throw TlvInvalidException("Invalid bytes for tlv-data: ${pdu.readableBytes()} < ${dataLen}")

            val dataBuf = pdu.readBytes(dataLen)

            val d1 = when (tag) {
                BaseParam.TAG -> TlvData1Base()

                RealtimeParam.TAG -> TlvData2Realtime()

                TerminalParam.TAG -> TlvData3Terminal()

                WarnParam.TAG -> TlvData4Warn()

                PeriodDataParam.TAG -> TlvData6Period()

                else -> TlvData(tag)
            }

            dataList.add(d1)

            // should exit! otherwise indefinitely loop~
            // ignore the rest bytes.
            if (tag in 1..6) {
                try {
                    d1.parse(dataBuf)
                } catch (ex: TlvException) {
                    d1.parseResult = ex
                    log.error("FAIL to parse data: ${ex.message}")
                }
            } else {
                log.error("UNKnown tag: ${tag}")
            }
        } //: while(pdu has more bytes)

        return buf
    }
}
